<template>
  <NDataTable
    size="small"
    :columns="columnList"
    :data="auditLogList"
    :striped="true"
    :loading="loading"
    :row-key="(data: AuditLog) => data.name"
  />
</template>

<script lang="tsx" setup>
import { file_google_rpc_error_details } from "@buf/googleapis_googleapis.bufbuild_es/google/rpc/error_details_pb";
import { createRegistry, fromBinary, toJsonString } from "@bufbuild/protobuf";
import dayjs from "dayjs";
import { ExternalLinkIcon } from "lucide-vue-next";
import { NButton, NDataTable, type DataTableColumn } from "naive-ui";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import BBAvatar from "@/bbkit/BBAvatar.vue";
import { useProjectV1Store, useUserStore } from "@/store";
import {
  projectNamePrefix,
  rolloutNamePrefix,
  getProjectIdRolloutUidStageUid,
} from "@/store/modules/v1/common";
import { getDateForPbTimestampProtoEs } from "@/types";
import { StatusSchema } from "@/types/proto-es/google/rpc/status_pb";
import type { AuditLog } from "@/types/proto-es/v1/audit_log_service_pb";
import {
  AuditDataSchema,
  AuditLog_Severity,
} from "@/types/proto-es/v1/audit_log_service_pb";
import { IssueService, type Issue } from "@/types/proto-es/v1/issue_service_pb";
import { file_v1_plan_service } from "@/types/proto-es/v1/plan_service_pb";
import { PlanService, type Plan } from "@/types/proto-es/v1/plan_service_pb";
import {
  RolloutService,
  type Rollout,
} from "@/types/proto-es/v1/rollout_service_pb";
import { SettingSchema } from "@/types/proto-es/v1/setting_service_pb";
import {
  SQLService,
  type ExportRequest,
} from "@/types/proto-es/v1/sql_service_pb";
import { humanizeDurationV1 } from "@/utils";
import { extractProjectResourceName } from "@/utils";
import JSONStringView from "./JSONStringView.vue";

type AuditDataTableColumn = DataTableColumn<AuditLog> & {
  hide?: boolean;
};

// The registry is used to decode anypb protobuf messages to JSON.
const registry = createRegistry(
  file_google_rpc_error_details,
  file_v1_plan_service
);

const props = withDefaults(
  defineProps<{
    auditLogList: AuditLog[];
    showProject?: boolean;
    loading?: boolean;
  }>(),
  {
    showProject: true,
    loading: false,
  }
);

const { t } = useI18n();
const projectStore = useProjectV1Store();
const userStore = useUserStore();

const columnList = computed((): AuditDataTableColumn[] => {
  return (
    [
      {
        key: "created-ts",
        title: t("audit-log.table.created-ts"),
        width: 240,
        render: (auditLog) =>
          dayjs(getDateForPbTimestampProtoEs(auditLog.createTime)).format(
            "YYYY-MM-DD HH:mm:ss Z"
          ),
      },
      {
        key: "severity",
        width: 96,
        title: t("audit-log.table.level"),
        render: (auditLog) => AuditLog_Severity[auditLog.severity],
      },
      {
        key: "project",
        width: 96,
        title: t("common.project"),
        hide: !props.showProject,
        render: (auditLog) => {
          const projectResourceId = extractProjectResourceName(auditLog.name);
          if (!projectResourceId) {
            return <span>-</span>;
          }
          const project = projectStore.getProjectByName(
            `${projectNamePrefix}${projectResourceId}`
          );
          return <span>{project.title}</span>;
        },
      },
      {
        key: "method",
        resizable: true,
        width: 256,
        title: t("audit-log.table.method"),
        render: (auditLog) => auditLog.method,
      },
      {
        key: "actor",
        width: 128,
        title: t("audit-log.table.actor"),
        render: (auditLog) => {
          const user = userStore.getUserByIdentifier(auditLog.user);
          if (!user) {
            return <span>-</span>;
          }
          return (
            <div class="flex flex-row items-center overflow-hidden gap-x-1">
              <BBAvatar size="SMALL" username={user.title} />
              <span class="truncate">{user.title}</span>
            </div>
          );
        },
      },
      {
        key: "request",
        resizable: true,
        minWidth: 256,
        width: 256,
        title: t("audit-log.table.request"),
        render: (auditLog) =>
          auditLog.request.length > 0 ? (
            <JSONStringView jsonString={auditLog.request} />
          ) : (
            "-"
          ),
      },
      {
        key: "response",
        resizable: true,
        minWidth: 256,
        width: 256,
        title: t("audit-log.table.response"),
        render: (auditLog) =>
          auditLog.response.length > 0 ? (
            <JSONStringView jsonString={auditLog.response} />
          ) : (
            "-"
          ),
      },
      {
        key: "status",
        resizable: true,
        width: 96,
        title: t("audit-log.table.status"),
        render: (auditLog) =>
          auditLog.status ? (
            <JSONStringView
              jsonString={toJsonString(StatusSchema, auditLog.status, {
                registry: registry,
              })}
            />
          ) : (
            "-"
          ),
      },
      {
        key: "latency",
        width: 96,
        title: t("audit-log.table.latency"),
        render: (auditLog) => {
          return <span>{humanizeDurationV1(auditLog.latency)}</span>;
        },
      },
      {
        key: "service-data",
        resizable: true,
        minWidth: 256,
        width: 256,
        title: t("audit-log.table.service-data"),
        render: (auditLog) => {
          return auditLog.serviceData && auditLog.serviceData.typeUrl ? (
            <JSONStringView
              jsonString={JSON.stringify(
                {
                  "@type": auditLog.serviceData.typeUrl,
                  ...getServiceDataValue(
                    auditLog.serviceData.typeUrl,
                    auditLog.serviceData.value
                  ),
                },
                (_, value) => {
                  if (typeof value === "bigint") {
                    return value.toString(); // Convert to string
                  }
                  return value;
                }
              )}
            />
          ) : (
            "-"
          );
        },
      },
      {
        key: "view",
        width: 60,
        title: t("common.view"),
        render: (auditLog) => {
          let link = getViewLink(auditLog);
          if (!link) {
            return null;
          }
          if (!link.startsWith("/")) {
            link = `/${link}`;
          }
          return (
            <a href={link} target="_blank">
              <NButton size="small" text type="primary">
                <ExternalLinkIcon class={"w-4"} />
              </NButton>
            </a>
          );
        },
      },
    ] as AuditDataTableColumn[]
  ).filter((column) => !column.hide);
});

const getServiceDataValue = (typeUrl: string, value: Uint8Array): any => {
  switch (typeUrl) {
    case "type.googleapis.com/bytebase.v1.AuditData":
      return fromBinary(AuditDataSchema, value);
    case "type.googleapis.com/bytebase.v1.Setting":
      return fromBinary(SettingSchema, value);
    default:
      return null;
  }
};

const getViewLink = (auditLog: AuditLog): string | null => {
  let parsedRequest: any;
  let parsedResponse: any;
  try {
    parsedRequest = JSON.parse(auditLog.request || "{}");
    parsedResponse = JSON.parse(auditLog.response || "{}");
  } catch {
    return null;
  }
  if (Boolean(parsedRequest["validateOnly"])) {
    return null;
  }

  const sections = auditLog.method.split("/").filter((i) => i);
  switch (sections[0]) {
    case RolloutService.typeName:
      return (parsedResponse as Rollout).name;
    case PlanService.typeName:
      return (parsedResponse as Plan).name;
    case IssueService.typeName:
      return (parsedResponse as Issue).name;
    case SQLService.typeName:
      if (sections[1] !== "Export") {
        return null;
      }
      const name = (parsedRequest as ExportRequest).name;
      if (!name) {
        return null;
      }
      const [projectId, rolloutId, _] = getProjectIdRolloutUidStageUid(name);
      if (!projectId || !rolloutId) {
        return null;
      }
      return `${projectNamePrefix}${projectId}/${rolloutNamePrefix}${rolloutId}`;
  }
  return null;
};
</script>
